#include "stdafx.h"
#include <boost/bind.hpp>
#include "../Utils/INIReader.h"

namespace ja2{ namespace utils
{
	PropertyContainer parse_ini_file(std::string const &File)
	{
		namespace qi = boost::spirit::qi;
		typedef std::string::const_iterator iterator;
		// Property container to use
		PropertyContainer properties;
		struct IniParser : qi::grammar<iterator>
		{
			//! Property container used.
			PropertyContainer &m_Container;
			//! Last category.
			std::string m_LastCategory;
			//! Last name.
			std::string m_LastName;

			// Define grammar
			qi::rule<iterator, char()> char_ident_start;
			qi::rule<iterator, char()> char_ident_middle;
			qi::rule<iterator, char()> char_ident_middle_strict;
			qi::rule<iterator, std::string()> ident;
			qi::rule<iterator, std::string()> ident_category;
			qi::rule<iterator> char_start_comment;
			qi::rule<iterator> blanks_p;
			qi::rule<iterator, std::string()> value_p;
			qi::rule<iterator> l_empty;
			qi::rule<iterator> l_comment; 
			qi::rule<iterator> c_comment_rule;
			qi::rule<iterator> b_comment; 
			qi::rule<iterator> l_category;
			qi::rule<iterator> l_entry;
			qi::rule<iterator, std::string()> l_value_p;
			qi::rule<iterator> l_value_empty;
			qi::rule<iterator, std::string()> l_value_single;
			qi::rule<iterator> l_value_coma;
			qi::rule<iterator> l_entry2;
			qi::rule<iterator> lines;
			qi::rule<iterator> start;

			// Construction/Destruction
			IniParser(PropertyContainer &Helper)
				: IniParser::base_type(start), m_Container(Helper)
			{
				char_ident_start %= qi::alpha | qi::char_('_');
				char_ident_middle %= qi::alnum | qi::char_('_') | qi::char_(' ') ;
				char_ident_middle_strict %= qi::alnum | qi::char_('_');
				ident %= char_ident_start >> *char_ident_middle_strict;
				ident_category %= char_ident_start >> *char_ident_middle;
				char_start_comment = qi::char_('#') | qi::char_(';') | qi::string("//");
				blanks_p = *qi::blank;
				value_p %= +(qi::alnum | (qi::punct - qi::char_(',')) | qi::blank);
				l_empty = blanks_p >> +qi::eol;
				l_comment = blanks_p >> char_start_comment >> * qi::print >> qi::eol; 
				c_comment_rule = boost::spirit::repository::confix("/*", "*/")[*(qi::char_ - qi::eol)];
				b_comment = 
					blanks_p >> 
					c_comment_rule >>
					blanks_p >> 
					qi::eol
					; 
				l_category = 
					blanks_p >> 
					qi::char_('[') >> 
					blanks_p >> 
					ident_category [ std::bind(&IniParser::add_section, this, std::placeholders::_1)] >>
					blanks_p >> 
					qi::char_(']') >> 
					blanks_p >> 
					qi::eol
					;
				l_value_p =
					blanks_p >>
					value_p >>
					blanks_p
					;
				l_value_empty =
					blanks_p >>
					qi::eol
					;
				l_value_single =
					l_value_p >>
					qi::eol
					;
				l_value_coma = 
					(+(l_value_p[std::bind(&IniParser::add_value, this, std::placeholders::_1)] >>
						-qi::char_(',')
						)
					)
					|
					l_value_p >>
					qi::eol
					;
				l_entry =  
					blanks_p >>
					ident [ std::bind(&IniParser::add_name, this, std::placeholders::_1)] >>
					blanks_p >>
					qi::char_('=') >> 
					(
						// No value
						l_value_empty
						|
						// Only 1 value
						l_value_single[std::bind(&IniParser::add_value, this, std::placeholders::_1)]
						|
						// Coma separated values
						l_value_coma
					)
					;
				l_entry2 =  
					blanks_p >>
					ident [ std::bind(&IniParser::add_name, this, std::placeholders::_1)] >>
					blanks_p >>
					qi::string("+=") >> 
					blanks_p >>
					value_p [std::bind(&IniParser::push_value, this, std::placeholders::_1)] >>
					blanks_p >>
					qi::eol
					;
				lines = l_comment | b_comment | l_category | l_entry | l_empty;
				start =  qi::lexeme[ * lines ];

				start.name("start");
				lines.name("lines");
				l_comment.name("l_comment");
				b_comment.name("b_comment");
				l_category.name("l_category");
				l_entry.name("l_entry");
				l_empty.name("l_empty");
				l_value_p.name("l_value_p");
				l_value_coma.name("l_value_coma");
				l_value_single.name("l_value_single");
				l_value_empty.name("l_value_empty");
			}

			//! Add section.
			void add_section(std::string const &Name)
			{
				m_LastCategory = Name;
				m_Container.get_section(m_LastCategory);
			}
			//! Add name (key).
			void add_name(std::string const &Str)
			{
				m_LastName = Str;
				// Create entry
				m_Container.get_section(m_LastCategory).add(m_LastName);
			}
			//! Add value
			void add_value(std::string const &Value)
			{
				m_Container.get_section(m_LastCategory).add(m_LastName, Value);
			}
			//! Push value to existing key.
			void push_value(std::string const &Value)
			{
				m_Container.get_section(m_LastCategory).set(m_LastName, Value);
			}
		};
		
		IniParser parser_instance(properties);
		iterator begin = File.begin(), end = File.end();
		if(!(qi::parse<>(begin, end, parser_instance) && begin == end))
			throw std::runtime_error("Error parsing INI file.");

		return std::move(properties);
	}
} /*utils*/} /*ja2*/